#pragma once

#include "tests.core.dual.h"
#include <vex/policy/bool_convertible.h>
#include <vex/policy/hashable.h>
#include <vex/policy/totally_ordered.h>
#include <vex/policy/merge_policies.h>
#include <vex/core/intrusive_ptr_support.h>
#include <boost/intrusive_ptr.hpp>

namespace vex {

    template<class T>
    struct handle_ptr_traits {
        typedef T element_type;
        typedef element_type* item_type;
    };

    template<class T> class handle_ptr 
        : public handle_ptr_traits<T>
        , public vex::policy::merge_policies<
            handle_ptr<T>, handle_ptr_traits<T>, vex::policy::empty_base,
            vex::policy::bool_convertible,
            vex::policy::hashable,
            vex::policy::totally_ordered
        >::type
    {
        typedef boost::intrusive_ptr< handle<T> > iptr_type;
        typedef std::pair<iptr_type, T*> holder_type;
        holder_type m_holder;

        void set_holder(handle<T>* h, T* p, bool p_add_ref = false)
        {
            iptr_type(h, p_add_ref).swap(m_holder.first);
            m_holder.second = p;
        }

        void swap_holder(holder_type& p_holder)
        {
            m_holder.first.swap(p_holder.first);
            std::swap(m_holder.second, p_holder.second);
        }
    public:
        handle_ptr() 
            : m_holder(nullptr, nullptr) 
        {
        }

        handle_ptr(handle<T>* h) 
            : m_holder(nullptr, nullptr)
        {
            T* p;
            if (h && h->query_interface(&p)) {
                this->set_holder(h, p);
            }
        }

        handle_ptr(T* p) 
            : m_holder(nullptr, nullptr)
        {
            handle<T>* h;
            if (p && p->query_handle(&h)) {
                this->set_holder(h, p);
            }
        }

        handle_ptr(handle<T>* h, T* p) 
            : m_holder(nullptr, nullptr)
        {
            set_holder(h, p);
        }

        template<class U> handle_ptr(handle_ptr<U> const & p_other, typename std::enable_if<
            std::is_convertible<U*, T*>::value>::type* = 0)
            : m_holder(nullptr, nullptr)
        {
            if (p_other) {
                T* p = p_other.get();
                handle<T>* h;
                if (p->query_handle(&h)) {
                    set_holder(h, p);
                }
            }
        }

        template<class U> handle_ptr(vex::core::handle_from_base<U>* p_hfb, typename std::enable_if<
            std::is_convertible<vex::core::handle_from_base<U>*, handle<T>*>::value &&
            std::is_convertible<U*, T*>::value>::type* = 0)
            : m_holder(nullptr, nullptr)
        {
            if (p_hfb) {
                handle<T>* h = p_hfb;
                T* p = p_hfb;
                set_holder(h, p);
            }
        }

        template<class U> handle_ptr(handle<U>* h, typename std::enable_if<
            std::is_convertible<U*, T*>::value>::type* = 0) 
            : m_holder(nullptr, nullptr)
        {
            U* p;
            if (h && h->query_interface(&p)) {
                handle<T>* t_h;
                T* t_p = p;
                if (t_p->query_handle(&t_h)) {
                    this->set_holder(t_h, t_p);
                }
            }
        }

        handle_ptr(handle_ptr&& p_other)
            : m_holder(std::move(p_other))
        {
            p_other.m_holder.second = nullptr;
        }

        handle_ptr& operator=(handle_ptr&& p_other)
        {
            handle_ptr(std::move(p_other))
                .swap(*this);
            return *this;
        }

        T* get() const
        {
            return m_holder.second;
        }

        T& operator*() const
        {
            assert(m_holder.first && m_holder.second != 0);
            return *m_holder.second;
        }

        T* operator->() const
        {
            assert(m_holder.first && m_holder.second != 0);
            return m_holder.second;
        }

        void swap(handle_ptr& p_other)
        {
            this->swap_holder(p_other.m_holder);
        }

        template<class U> 
        typename std::enable_if< 
            std::is_convertible<T*, U*>::value, handle_ptr<U>
        >::type query_handle() const
        {
            if (*this) {
                return handle_ptr<U>(*this);
            }
            
            return handle_ptr<U>();
        }
    };

    template<class T> T* get_pointer(handle_ptr<T> const & p_handle)
    {
        return p_handle.get();
    }

    template<class T, class U> handle_ptr<T> static_pointer_cast(handle_ptr<U> const & p_handle)
    {
        return p_handle;
    }
}
